package com.bea.ftd.encrypt.service.impl;


import com.bea.ftd.common.dao.SecretInfoRepo;
import com.bea.ftd.common.dao.SignInfoRepo;
import com.bea.ftd.common.entity.SecretInfo;
import com.bea.ftd.common.entity.SignInfo;
import com.bea.ftd.common.enums.EncryptEnum;
import com.bea.ftd.encrypt.service.GatewayEnOpStep;
import com.bea.ftd.encrypt.utils.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.*;
import java.net.Socket;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class GatewayEnOpStepImpl implements GatewayEnOpStep {

    @Autowired
    private SecretInfoRepo secretInfoRepo;

    @Autowired
    private SignInfoRepo signInfoRepo;

    private static final String DEFAULT_CODEING = "UTF-8";

    private static final Logger log = LoggerFactory.getLogger(GatewayEnOpStepImpl.class);
    @Override
    @Cacheable(value = "SecretInfo")
    public String SendEncryHost(byte[] msg, int n,String partnerCode) {
        DataInputStream in = null;
        DataOutputStream out = null;
        Socket socket = null;

        try {
            SecretInfo secretInfoRepo = this.secretInfoRepo.findByPartnerCode(partnerCode);
            if(StringUtils.isEmpty(secretInfoRepo)){
                return "参数错误";
            }
            String gatewayIp = secretInfoRepo.getEncryptHardwareIp();
            int gatewayPort = secretInfoRepo.getEncryptHardwarePort();
            socket = new Socket(gatewayIp, gatewayPort);
            log.info("连接加密机ip......[{}]，连接加密机port......[{}]",gatewayIp,gatewayPort);
            // 设置超时时间为 5 秒
            socket.setSoTimeout(5*1000);
            in = new DataInputStream(socket.getInputStream());
            out = new DataOutputStream(socket.getOutputStream());
            // 上送报文到加密机网关
            out.write(msg, 0, msg.length);
            log.info("上送报文的长度......[{}]，上送报文的内容......[{}]",msg.length,new String(msg));
            out.flush();
            // 加密网关返回报文
            byte[] b = new byte[n];
            for (int i = 0; i < n; i++) {
                b[i] = (byte) in.read();
            }
            //首先获取加密网关返回的错误码，如果是S开头，则成功
            String re = new String(b,"GBK");
            log.info("返回报文长度......[{}]",re.length());
            String returnCode = re.substring(50);
            log.info("加密机返回返回码......[{}]",returnCode);
            int num = 32;
            String reMsg = null;
            byte[] bt = new byte[num];
            if(returnCode.startsWith("S")){
                for (int i = 0; i < num; i++) {
                    bt[i] = (byte) in.read();
                }
                reMsg = new String(bt,"GBK");
                log.info("加密网关返回报文体长度......[{}],加密机网关返回报文体数据......[{}]",reMsg.length(),reMsg);
            }else{
                log.info("调用加密机网关错误，返回返回码......[{}]",returnCode);
                reMsg = "passGateway eror:" + returnCode;
            }
            //转换为16进制
            printHexString("-------16进制返回报文:------\n", b);

            return reMsg;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (in != null){
                    in.close();
                }
                if (out != null){
                    out.close();
                }
                if (socket != null){
                    socket.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    @Override
    @Cacheable(value = "SignInfo")
    public String generateSignature(String partnerCode, String data) {
        try {
            SignInfo signInfo = this.signInfoRepo.findByPartnerCode(partnerCode);
            if(StringUtils.isEmpty(signInfo)){
                log.info("电商编号为......[{}]内容为......[{}]",partnerCode,data);
                return "0";
            }
            String privatePath = signInfo.getPrivateKeyPath();//私钥
            String signType = signInfo.getSignType( );//签名算法
            Properties properties = new Properties();
            BufferedReader bufferedReader = null;
            try {
                bufferedReader = new BufferedReader(new FileReader(privatePath));
                properties.load(bufferedReader);
            } catch (IOException e) {
                e.printStackTrace();
            }
            String privateKey = properties.getProperty("ant.privateKey");
            byte[] bytesPrivate = Base64.getDecoder().decode(privateKey.getBytes());
            PKCS8EncodedKeySpec keySpecprivate = new PKCS8EncodedKeySpec(bytesPrivate);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PrivateKey generatedPrivateKey = keyFactory.generatePrivate(keySpecprivate);

            Signature signature = Signature.getInstance(signType);
            signature.initSign(generatedPrivateKey);
            if (null != data) {
                signature.update(data.getBytes(DEFAULT_CODEING));
            }
            byte[] signed = signature.sign();
            return new String(Base64.getEncoder().encode(signed));
        } catch (Exception e) {
            log.error(String.format("Generating signature with error : ", e.getMessage()));
            e.printStackTrace();
            return "0";
        }
    }

    @Override
    @Cacheable(value = "SignInfo")
    public boolean verifySiginature(String partnerCode, String signatureStr, String context) {
        try {
            SignInfo signInfo = this.signInfoRepo.findByPartnerCode(partnerCode);
            if(StringUtils.isEmpty(signInfo)){
                log.info("电商编号为......[{}]内容为......[{}]",partnerCode,context);
                return false;
            }
            String publicPath = signInfo.getPublicKeyPath();//公钥
            String signType = signInfo.getSignType();//签名算法
            Properties properties = new Properties();
            BufferedReader bufferedReader = null;
            try {
                bufferedReader = new BufferedReader(new FileReader(publicPath));
                properties.load(bufferedReader);
            } catch (IOException e) {
                e.printStackTrace();
            }
            String publicKey = properties.getProperty("ant.publicKey");
            byte[] newSigned = Base64.getDecoder().decode(context);
            byte[] bytesPublic = Base64.getDecoder().decode(publicKey.getBytes());
            X509EncodedKeySpec keySpecPublic = new X509EncodedKeySpec(bytesPublic);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey generatedPublicKey = keyFactory.generatePublic(keySpecPublic);
            Signature signature = Signature.getInstance(signType);
            signature.initVerify(generatedPublicKey);
            signature.update(signatureStr.getBytes(DEFAULT_CODEING));
            boolean verify = signature.verify(newSigned);
            return verify;
        } catch (Exception e) {
            log.error(String.format("Verify signature with error : ", e.getMessage()));
            e.printStackTrace();
            return false;
        }
    }

    @Override
    @Cacheable(value = "SignInfo")
    public String Sm2Encrypt(String partnerCode, String data) {
        SignInfo signInfo = this.signInfoRepo.findByPartnerCode(partnerCode);
        if(StringUtils.isEmpty(signInfo)){
            log.info("电商编号为......[{}]内容为......[{}]",partnerCode,data);
            return "0";
        }
        String privatePath = signInfo.getPrivateKeyPath();//私钥
        Properties properties = new Properties();
        BufferedReader bufferedReader = null;
        byte[] sourceData = data.getBytes();
        String cipherText =null;
        try {
            bufferedReader = new BufferedReader(new FileReader(privatePath));
            properties.load(bufferedReader);
            String privateKey = properties.getProperty("sm2.privateKey");
            cipherText = SM2Utils.encrypt(Util.hexToByte(privateKey), sourceData);
        } catch (IOException e) {
            e.printStackTrace();
            return "0";
        }
        return cipherText;
    }

    @Override
    @Cacheable(value = "SignInfo")
    public String Sm2Decrypt(String partnerCode, String data) {
        SignInfo signInfo = this.signInfoRepo.findByPartnerCode(partnerCode);
        if(StringUtils.isEmpty(signInfo)){
            log.info("电商编号为......[{}]内容为......[{}]",partnerCode,data);
            return "0";
        }
        String publicPath = signInfo.getPublicKeyPath();//公钥
        Properties properties = new Properties();
        BufferedReader bufferedReader = null;

        byte[] sourceData = data.getBytes();
        String plainText = null;
        try {
            bufferedReader = new BufferedReader(new FileReader(publicPath));
            properties.load(bufferedReader);
            String publicKey = properties.getProperty("sm2.publicKey");
            plainText = new String(SM2Utils.decrypt(Util.hexToByte(publicKey), Util.hexToByte(data)));
        } catch (IOException e) {
            e.printStackTrace();
            return "0";
        }
        return plainText;
    }

    @Override
    @Cacheable(value = "SignInfo")
    public String EncryptECB(String partnerCode, String data) {
        boolean hexString = false;
        SignInfo signInfo = this.signInfoRepo.findByPartnerCode(partnerCode);
        if(StringUtils.isEmpty(signInfo)){
            log.info("电商编号为......[{}]内容为......[{}]",partnerCode,data);
            return "参数错误";
        }
        String signType = signInfo.getSignType();//签名算法
        try
        {
            SM4_Context ctx = new SM4_Context();
            ctx.isPadding = true;
            ctx.mode = SM4.SM4_ENCRYPT;

            byte[] keyBytes;
            if (hexString)
            {
                keyBytes = Util.hexStringToBytes(signType);
            }
            else
            {
                keyBytes = signType.getBytes();
            }

            SM4 sm4 = new SM4();
            sm4.sm4_setkey_enc(ctx, keyBytes);
            byte[] encrypted = sm4.sm4_crypt_ecb(ctx, data.getBytes("GBK"));
            String cipherText = new BASE64Encoder().encode(encrypted);
            if (cipherText != null && cipherText.trim().length() > 0)
            {
                Pattern p = Pattern.compile("\\s*|\t|\r|\n");
                Matcher m = p.matcher(cipherText);
                cipherText = m.replaceAll("");
            }
            return cipherText;
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    @Cacheable(value = "SignInfo")
    public String DecryptECB(String partnerCode, String data) {
        boolean hexString = false;
        SignInfo signInfo = this.signInfoRepo.findByPartnerCode(partnerCode);
        if(StringUtils.isEmpty(signInfo)){
            log.info("电商编号为......[{}]内容为......[{}]",partnerCode,data);
            return "参数错误";
        }
        String signType = signInfo.getSignType();//签名算法
        try
        {
            SM4_Context ctx = new SM4_Context();
            ctx.isPadding = true;
            ctx.mode = SM4.SM4_DECRYPT;

            byte[] keyBytes;
            if (hexString)
            {
                keyBytes = Util.hexStringToBytes(signType);
            }
            else
            {
                keyBytes = signType.getBytes();
            }

            SM4 sm4 = new SM4();
            sm4.sm4_setkey_dec(ctx, keyBytes);
            byte[] decrypted = sm4.sm4_crypt_ecb(ctx, new BASE64Decoder().decodeBuffer(data));
            return new String(decrypted, "GBK");
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    @Cacheable(value = "SignInfo")
    public String EncryptCBC(String partnerCode, String data) {
        boolean hexString = false;
        SignInfo signInfo = this.signInfoRepo.findByPartnerCode(partnerCode);
        if(StringUtils.isEmpty(signInfo)){
            log.info("电商编号为......[{}]内容为......[{}]",partnerCode,data);
            return "参数错误";
        }
        String signType = signInfo.getSignType();//签名算法
        String secretKey = "JeF8U9wHFOMfs2Y8";
        String iv = signType;
        try
        {
            SM4_Context ctx = new SM4_Context();
            ctx.isPadding = true;
            ctx.mode = SM4.SM4_ENCRYPT;

            byte[] keyBytes;
            byte[] ivBytes;
            if (hexString)
            {
                keyBytes = Util.hexStringToBytes(secretKey);
                ivBytes = Util.hexStringToBytes(iv);
            }
            else
            {
                keyBytes = secretKey.getBytes();
                ivBytes = iv.getBytes();
            }

            SM4 sm4 = new SM4();
            sm4.sm4_setkey_enc(ctx, keyBytes);
            byte[] encrypted = sm4.sm4_crypt_cbc(ctx, ivBytes, data.getBytes("GBK"));
            String cipherText = new BASE64Encoder().encode(encrypted);
            if (cipherText != null && cipherText.trim().length() > 0)
            {
                Pattern p = Pattern.compile("\\s*|\t|\r|\n");
                Matcher m = p.matcher(cipherText);
                cipherText = m.replaceAll("");
            }
            return cipherText;
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    @Cacheable(value = "SignInfo")
    public String DecryptCBC(String partnerCode, String data) {
        boolean hexString = false;
        SignInfo signInfo = this.signInfoRepo.findByPartnerCode(partnerCode);
        if(StringUtils.isEmpty(signInfo)){
            log.info("电商编号为......[{}]内容为......[{}]",partnerCode,data);
            return "参数错误";
        }
        String signType = signInfo.getSignType();//签名算法
        String secretKey = EncryptEnum.SM4ECB.getName();
        String iv = signType;
        try
        {
            SM4_Context ctx = new SM4_Context();
            ctx.isPadding = true;
            ctx.mode = SM4.SM4_DECRYPT;

            byte[] keyBytes;
            byte[] ivBytes;
            if (hexString)
            {
                keyBytes = Util.hexStringToBytes(secretKey);
                ivBytes = Util.hexStringToBytes(iv);
            }
            else
            {
                keyBytes = secretKey.getBytes();
                ivBytes = iv.getBytes();
            }

            SM4 sm4 = new SM4();
            sm4.sm4_setkey_dec(ctx, keyBytes);
            byte[] decrypted = sm4.sm4_crypt_cbc(ctx, ivBytes, new BASE64Decoder().decodeBuffer(data));
            return new String(decrypted, "GBK");
        }
        catch (Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }


    /**
     * 将指定byte数组以16进制的形式打印到控制台
     *
     * @param hint
     *            String
     * @param b
     *            byte[]
     * @return void
     */
    public static void printHexString(String hint, byte[] b) {
        for (int i = 0; i < b.length; i++) {
            String hex = Integer.toHexString(b[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
        }
    }
}